﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Logo.command;

namespace Logo
{
    class Invoker
    {
        char[] WHITESPACE = { ' ', '\t', '\n', '\r' };
        char LEFT_BRACKET = '[';
        char RIGHT_BRACKET = ']';
        char[] OPERATOR = { '-', '*', '/', '+' };
        public static String[] CONDITIONAL_OPERATOR = { "==", "<", ">", "<=", ">=" };

        String FORWARD = "FORWARD";
        String RIGHT = "RIGHT";
        String LEFT = "LEFT";
        String REPEAT = "REPEAT";
        String PENUP = "PENUP";
        String PENDOWN = "PENDOWN";
        String TO = "TO";
        String END = "END";
        String IF = "IF";

        String code = "";
        int length = 0;
        String token = "";
        int charPos = 0;

        // Init function variables
        String fName = "";
        String fMember = "";
        bool stateEnd = false;
        public static Dictionary<String, FunctionMaker> functions;

        private LogoDraw logo;
        private Dictionary<String, String> dicParam;

        private MacroCommand macroCommand;
        public MacroCommand MacroCommand
        {
            get { return macroCommand; }
        }

        public Invoker(LogoDraw logo, String code, Dictionary<String, String> dicParam)
        {
            this.logo = logo;
            macroCommand = new MacroCommand(logo);
            this.dicParam = dicParam;

            this.code = code.ToUpper();
            this.length = code.Length;
        }

        public void SkipWhiteSpace()
        {
            while (charPos < length && WHITESPACE.Contains(code[charPos]))
            {
                ++charPos;
            }
        }

        public String GetNextToken()
        {
            // Skip blank
            SkipWhiteSpace();

            token = "";
            while (charPos < length && !WHITESPACE.Contains(code[charPos]) && code[charPos] != LEFT_BRACKET)
            {
                token += code[charPos++];
            }

            return token;
        }

        public String PeekNextToken()
        {
            // Remember current charPos
            int tempCharPos = charPos;

            // Get Next content
            String nextToken = GetNextToken();

            // Set back charPos
            charPos = tempCharPos;

            return nextToken;
        }

        public String GetParameter()
        {
            GetNextToken();

            // Evaluate Expression
            return token = EvaluateExpression(token);
        }

        public String EvaluateExpression(String token)
        {
            int pos = 0;

            // Get Operator
            while (pos < token.Length && !OPERATOR.Contains(token[pos]))
            {
                ++pos;
            }

            // One Operan
            if (pos >= token.Length)
            {
                return IsParam(token) ? dicParam[token.Substring(1)] : token;
            }

            // Operan 1
            String operan1 = token.Substring(0, pos);
            operan1 = IsParam(operan1) ? dicParam[operan1.Substring(1)] : operan1;
            int intOperan1 = int.Parse(operan1);

            // Operan 2
            String operan2 = token.Substring(pos + 1);
            operan2 = IsParam(operan2) ? dicParam[operan2.Substring(1)] : operan2;
            int intOperan2 = int.Parse(operan2);

            // opeartor
            char op = token[pos];
            string result = "";
            if (op == '-') result = (intOperan1 - intOperan2).ToString();
            if (op == '*') result = (intOperan1 * intOperan2).ToString();
            if (op == '+') result = (intOperan1 + intOperan2).ToString();
            if (op == '/') result = (intOperan1 / intOperan2).ToString();

            return result; ;
        }

        private List<String> GetFunctionParams()
        {
            List<String> fParams = new List<String>();

            while (IsParam(PeekNextToken()))
            {
                // Add param without ':' character
                fParams.Add(GetNextToken().Substring(1));
            }

            return fParams;
        }

        public String GetFunctionMember()
        {
            // Skip whitespace
            SkipWhiteSpace();

            // Init
            String member = "";
            stateEnd = false;

            while (charPos < length && !stateEnd)
            {
                if ((code[charPos].ToString() + code[charPos + 1].ToString() + code[charPos + 2].ToString()).Equals(END) && WHITESPACE.Contains(code[charPos + 3]))
                {
                    stateEnd = true;
                    charPos--;
                }
                member += code[charPos];
                charPos++;
            }

            return member;
        }

        public String GetBracketContent()
        {
            SkipWhiteSpace();

            String content = "";
            int number = 1;

            if (code[charPos++] == LEFT_BRACKET)
            {
                while (charPos < length && !(number == 1 && code[charPos] == RIGHT_BRACKET))
                {
                    if (code[charPos] == LEFT_BRACKET)
                    {
                        ++number;
                    }
                    else if (code[charPos] == RIGHT_BRACKET)
                    {
                        --number;
                    }
                    content += code[charPos++];
                }

                if (charPos >= length)
                {
                    throw new Exception("Bracket not complete");
                }
                else
                {
                    ++charPos;
                }
            }
            else
            {
                throw new Exception("Bracket not complete");
            }

            return content;
        }

        public bool IsParam(String token)
        {
            return token != null && token.Length > 1 && token[0] == ':';
        }

        public void Run()
        {
            Parse();
            macroCommand.Execute();
        }

        public void Parse()
        {
            while (charPos < length)
            {
                GetNextToken();

                token = token.ToUpper();

                if (token.Equals(FORWARD))
                {
                    ForwardParse();
                }
                else if (token.Equals(RIGHT))
                {
                    RightParse();
                }
                else if (token.Equals(LEFT))
                {
                    LeftCommand();
                }
                else if (token.Equals(PENUP))
                {
                    PenUpParse();
                }
                else if (token.Equals(PENDOWN))
                {
                    PenDownParse();
                }
                else if (token.Equals(REPEAT))
                {
                    RepeatParse();
                }
                else if (token.Equals(TO))
                {
                    ToParse();
                }
                else if (token.Equals(END))
                {
                    EndParse();
                }
                else if (Invoker.functions.ContainsKey(token))
                {
                    FunctionParse(Invoker.functions[token]);
                }
                else if (token.Equals(IF))
                {
                    IfParse();
                }
                else if (token.Equals(""))
                {
                    // Do nothing
                }
                else
                {
                    throw new Exception("Operation " + token + " not found");
                }
            }
        }

        private void RepeatParse()
        {
            GetNextToken();
            int numberLoop = 0;
            try
            {
                numberLoop = int.Parse(token);
            }
            catch (Exception e)
            {
                throw new Exception("Number loop not found at REPEAT");
            }

            string data = GetBracketContent();

            Invoker invoker = new Invoker(logo, data, dicParam);
            invoker.Parse();

            macroCommand.Add(new RepeatCommand(logo, numberLoop, invoker.MacroCommand));
        }

        private void PenDownParse()
        {
            macroCommand.Add(new PenDownCommand(logo));
        }

        private void PenUpParse()
        {
            macroCommand.Add(new PenUpCommand(logo));
        }

        private void LeftCommand()
        {
            GetParameter();
            double angle = 0;
            try
            {
                angle = double.Parse(token);
            }
            catch (Exception e)
            {
                new Exception("Angle not found at LEFT");
            }

            macroCommand.Add(new LeftCommand(logo, angle));
        }

        private void RightParse()
        {
            GetParameter();
            double angle = 0;
            try
            {
                angle = double.Parse(token);
            }
            catch (Exception e)
            {
                new Exception("Angle not found at RIGHT");
            }

            macroCommand.Add(new RightCommand(logo, angle));
        }

        private void ForwardParse()
        {
            GetParameter();
            int distance = 0;
            try
            {
                distance = int.Parse(token);
            }
            catch (Exception e)
            {
                throw new Exception("Distance not found at FORWARD");
            }

            macroCommand.Add(new ForwardCommand(logo, distance));
        }

        private void ToParse()
        {
            fName = GetNextToken();
            FunctionMaker function = new FunctionMaker(fName.ToUpper(), GetFunctionParams(), GetFunctionMember());
            Invoker.functions.Add(fName, function);
        }

        private void EndParse()
        {
            charPos += 2;
        }

        private void FunctionParse(FunctionMaker function)
        {
            // Set Param Dictionary
            Dictionary<String, String> dic = new Dictionary<string, string>();

            // Get Parameters
            for (int i = 0; i < function.Parameters.Count; ++i)
            {
                dic.Add(function.Parameters[i], GetParameter());
            }

            // Create Invoker
            Invoker fInvoker = new Invoker(logo, function.FunctionMember, dic);
            fInvoker.Parse();

            // Add to macro command
            macroCommand.Add(fInvoker.MacroCommand);
        }

        private void IfParse()
        {
            int pos = 0;
            int state = 0;
            int operan1 = 0;
            string op = "";
            int operan2 = 0;
            String temp;

            // Get if data
            String ifToken = "";
            while (charPos < code.Length && code[charPos] != LEFT_BRACKET)
            {
                ifToken += code[charPos++];
            }

            while (pos < ifToken.Length)
            {
                // Skip white space
                while (pos < ifToken.Length && WHITESPACE.Contains(ifToken[pos]))
                {
                    ++pos;
                }

                // Get token
                temp = "";
                while (pos < ifToken.Length && !WHITESPACE.Contains(ifToken[pos]))
                {
                    temp += ifToken[pos++];
                }

                if (state == 0)
                {
                    // Get operan 1;
                    operan1 = int.Parse(IsParam(temp) ? dicParam[temp.Substring(1)] : temp);
                    state = 1;
                }
                else if (state == 1)
                {
                    // Get operator
                    op = temp;
                    state = 2;
                }
                else if (state == 2)
                {
                    // Get operan 2;
                    operan2 = int.Parse(IsParam(temp) ? dicParam[temp.Substring(1)] : temp);
                    break;
                }
            }

            // Get data
            string data = GetBracketContent();

            // Check condition
            bool result = false;
            if (op == "==") result = operan1 == operan2;
            else if (op == "<") result = operan1 < operan2;
            else if (op == ">") result = operan1 > operan2;
            else if (op == "<=") result = operan1 <= operan2;
            else if (op == ">=") result = operan1 >= operan2;

            if (result)
            {
                // Condition satisty
                Invoker invoker = new Invoker(logo, data, dicParam);
                invoker.Parse();
                macroCommand.Add(invoker.MacroCommand);
            }
        }
    }
}
